Beginners Programming
Roll Those Bones
The game of Craps requires two dice to be thrown. Since our goal is to eventually make a graphical version of this game, let's work on developing a dice roller program. I will use the dice outlines that were developed earlier in this article, but I will need to have code that can draw any of the six faces on either of two dice outlines. This is what the window looks like with the placement of the dice outlines:

The window is 260 pixels high and 230 pixels wide. The placement of the dice outlines corresponds to the placement of the first two dice we drew in the earlier dice program, specifically 20 pixels from left side of the window to the first dice. The dice is 80 pixels wide, then 40 pixels between the first and second dice. The second dice is 80 pixels wide also. Both dice are 20 pixels from the top of the window and both dice are 80 pixels high.
The window to be opened will be a Graphics window without scrollbars. This will allow us to draw graphics. It will have two Command Buttons as seen above. We will need to set up event handlers for three events: Roll Dice Click, Quit Button Click and Close Window. The last two events can use the same event handler as they perform the same task.
Dice are rolled using the random number generator which we talked about some time back. It produces a random number using the RND function. The result of the RND function is a fractional value between 0 and 1 (but not including 0 or 1), which we will need to scale using arithmetic. Go back to the first and second installments to review the use of the RND function and how to scale the output. I will simply give you the statement that produces a whole number between 1 and 6 (inclusive):
dice = int(rnd(0)*6)+1
Let's examine the code fragment that is used to open the window:
NOMAINWIN
WindowWidth = 260
WindowHeight = 230
UpperLeftX = 40
UpperLeftY = 40
'buttons go here --------------------
Open "Roll Dice" for Graphics_nsb as #main
By now I am hoping that the code to open a window is starting to look a little bit familiar. What is missing is the buttons. I put a comment in there noting where the BUTTON statements go - lets examine them:
button #main, "Quit",[quit],UL, 130, 150, 100, 25 button #main, "Roll Dice",[roll],UL, 15, 150, 100, 25
We spent quite a bit of time on Buttons, you should be able to see that these buttons will be placed on the window specified by the handle #main (the only window we will have). One will be labeled "Quit" and the other "Roll Dice". Each of them have an event handler specified - one is [quit] and the other is [roll]. Notice each are anchored to the upper left corner of the window. The series of numbers are the X, Y placement of each button followed by the size of each button. I chose a uniform size for both - 100 x 25. Placement was a series of trial and error corrections to the X and Y coordinates of each button, running the program to inspect and adjusting each time, until I was satified with the result.
After the window is open we need to do a few additional things to achieve the final initialized state. We need to fill the window with white, set up the close window trap, set the default text font and draw the dice outlines. Below is the code to do these things. We have reviewed each of these statements in detail earlier in this article.
#main "trapclose [quit]"
#main "down; fill white; up; flush"
#main "font ms_sans_serif 10"
'show dice outlines
#main "goto 20 20; down; box 100 100; up"
#main "goto 140 20; down; box 220 100; up"
#main "flush"
Notice one thing: the event handler for the TrapClose event is the very same as the event handler for the Quit button click event. Liberty Basic allows us to share these events without issue. This is a simple form of Code-Reuse.
Now that the window is completely initialized, we need to wait. Well add a WAIT statement that is. I will also add skeleton event handlers for each of the events we have setup, so that we can actually run the program. By the way, a skeleton event handler does nothing, it mearly waits. It is a place keeper that we can add to our code, so that the program is technically complete, although it is not functionally complete. In that state it will run.
Here is the program with the remaining code. The [quit] event has been given the END statement so that the program will actually quit.
NOMAINWIN
WindowWidth = 260
WindowHeight = 230
UpperLeftX = 40
UpperLeftY = 40
button #main, "Quit",[quit],UL, 130, 150, 100, 25
button #main, "Roll Dice",[roll],UL, 15, 150, 100, 25
Open "Roll Dice" for Graphics_nsb as #main
#main "trapclose [quit]"
#main "down; fill white; up; flush"
#main "font ms_sans_serif 10"
'show dice outlines
#main "goto 20 20; down; box 100 100; up"
#main "goto 140 20; down; box 220 100; up"
#main "flush"
Wait
[quit]
close #main
END
[roll]
wait
Many Faces
Choosing the random number that each of the dice will display is fairly easy, displaying any one of the six faces on a given dice is a bit more difficult. But let's think about this a moment. There are some common characteristics between the dots that we need to draw on each face. For instance, consider the values of 1, 3 and 5 as they are shown on a dice. Each of them (and only these) has a center dot. Every dice face for a value greater than 1 (e.g. 2, 3, 4, 5 and 6) has dots in the upper left corner and the lower right corner. Every dice face for a value greater than 3 has dots in all four corners of the dice and finally, only 6 has dots in the center right hand and left hand locations.
From this observation we can create some simple rules. I will describe them in pseudo code:
Since we worked out the basic drawing rules of the dice faces, I will not go into how to do this again, however using the earlier work combined with the rules above, I was able to create the following code to draw any one face on a dice:
#main "backcolor black"
if dice = 1 or dice = 3 or dice = 5 then
#main "up; goto 60 60"
#main "down; circlefilled 5"
end if
if dice > 1 then
#main "up; goto 35 35"
#main "down; circlefilled 5"
#main "up; goto 85 85"
#main "down; circlefilled 5"
end if
if dice > 3 then
#main "up; goto 85 35"
#main "down; circlefilled 5"
#main "up; goto 35 85"
#main "down; circlefilled 5"
end if
if dice = 6 then
#main "up; goto 35 60"
#main "down; circlefilled 5"
#main "up; goto 85 60"
#main "down; circlefilled 5"
end if
The other dice face is offset by 120 pixels in the X direction, so all the X values will be 120 larger to draw the value of the second dice on the second dice face. Here is the code to draw this dice:
#main "backcolor black"
if dice = 1 or dice = 3 or dice = 5 then
#main "up; goto 180 60"
#main "down; circlefilled 5"
end if
if dice > 1 then
#main "up; goto 155 35"
#main "down; circlefilled 5"
#main "up; goto 205 85"
#main "down; circlefilled 5"
end if
if dice > 3 then
#main "up; goto 205 35"
#main "down; circlefilled 5"
#main "up; goto 155 85"
#main "down; circlefilled 5"
end if
if dice = 6 then
#main "up; goto 155 60"
#main "down; circlefilled 5"
#main "up; goto 205 60"
#main "down; circlefilled 5"
end if
What remains is the code to actually roll the dice. The process of rolling the dice will be as follows (in pseudo code):
I have developed the following code to accomplish the work specified above. It is added to the [roll] event handler:
[roll]
'clear any used memory
#main "discard"
'clear last dice
#main "backcolor white"
#main "up; goto 20 20; down; boxfilled 100 100; up"
#main "goto 140 20; down; boxfilled 220 100; up"
'choose some dice values
dice = int(rnd(0)*6)+1
gosub [showDice1]
dice = int(rnd(0)*6)+1
gosub [showDice2]
wait
There are a couple new things there - lets talk about them. The first is the discard command that is set to the graphics window. It is a graphics command that instructs Liberty Basic to dump any drawn graphics that have not been flushed from memory. As you will remember from our discussion about flushing graphics earlier, it is the process of commiting a drawn segment of graphics and making it stick to the window. Unflushed graphics are volatile and can be lost easily if a window or other object is placed over the unflushed drawing segments. If you will notice, we are not flushing any of our "dots" that we are creating with filled circles - in spite of that, they are consuming memory. If we did not reclaim (or return the memory back to Liberty Basic) we would eventually run out of resources running the roll dice program and it would crash. Discard returns the memory back to Liberty Basic that the unflushed graphics are using and prevents the program from crashing.
We reset the dice to balnk white boxes by using the boxfilled command. It is just like the box command, except it fills the box with the current backcolor. Remember the circlefilled command - it also fills the circles with the current backcolor, which we set to black so we can get black dots. In order to get white filled boxes, we need to set the backcolor to white.
Notice also in the code above, that I did not place the actual drawing of the dot in-line with the dice rolling code, rather it is accessed with a GOSUB. The GOSUB is almost like the GOTO command - with one very important difference. When executed, Liberty Basic remembers what line of code would have been next had the GOSUB not been in the code. It does this because it expects to encounter a RETURN command later that will cause the execution of the program to literally return to the spot it has memorized. In this way we have created a sub-routine. A sub-routine is a unit of code that is encapsulated between a label (the part that is defined with brackets) and a RETURN statement.
Sub-routines allow us to simplify our code by removing large chunks of logically related operations out of the main body of the code to a separate location in our program. The sub-routine is accessed by the use of a GOSUB command which redirects the program execution to the sub-routine until the RETURN is encountered. We can also use this technique to allow us to re-use a certain chunk of code that might be common to a program from many different areas.
The important this to remember is that every GOSUB must have a RETURN. If you use GOSUB, but you never intend to return back to the place you branched from, then what you really mean is GOTO. Failure to return from a GOSUB will eventually cause your program to operate unpredictably. Likewise, if Liberty Basic encounters a RETURN statement and a GOSUB has not yet been executed, the program will halt in an error condition.
Well, for all of that scary warning stuff and technical stuff, I would again recommend not to worry too much. It will all come into clarity over time. But it does take practice and experimentation. Try stuff. Even a crash is not bad - you will learn from it.
Because I have decided not to have the dot drawing code in line, I will need to add labels at the beginning of each of the drawing routines. The first is called [showDice1] and the second is called [showDice2]. Also both require a RETURN statement at the end of the code section.
This is good, the program as it stands runs and will show us the dice that we randomly chose. Here is the code to this point:
NOMAINWIN
WindowWidth = 260
WindowHeight = 230
UpperLeftX = 40
UpperLeftY = 40
button #main, "Quit",[quit],UL, 130, 150, 100, 25
button #main, "Roll Dice",[roll],UL, 15, 150, 100, 25
Open "Roll Dice" for Graphics_nsb as #main
#main "trapclose [quit]"
#main "down; fill white; up; flush"
#main "font ms_sans_serif 10"
'show dice outlines
#main "goto 20 20; down; box 100 100; up"
#main "goto 140 20; down; box 220 100; up"
#main "flush"
Wait
[quit]
close #main
END
[roll]
'clear any used memory
#main "discard"
'clear last dice
#main "backcolor white"
#main "up; goto 20 20; down; boxfilled 100 100; up"
#main "goto 140 20; down; boxfilled 220 100; up"
'choose some dice values
dice = int(rnd(0)*6)+1
gosub [showDice1]
dice = int(rnd(0)*6)+1
gosub [showDice2]
wait
[showDice1]
#main "backcolor black"
if dice = 1 or dice = 3 or dice = 5 then
#main "up; goto 60 60"
#main "down; circlefilled 5"
end if
if dice > 1 then
#main "up; goto 35 35"
#main "down; circlefilled 5"
#main "up; goto 85 85"
#main "down; circlefilled 5"
end if
if dice > 3 then
#main "up; goto 85 35"
#main "down; circlefilled 5"
#main "up; goto 35 85"
#main "down; circlefilled 5"
end if
if dice = 6 then
#main "up; goto 35 60"
#main "down; circlefilled 5"
#main "up; goto 85 60"
#main "down; circlefilled 5"
end if
return
[showDice2]
#main "backcolor black"
if dice = 1 or dice = 3 or dice = 5 then
#main "up; goto 180 60"
#main "down; circlefilled 5"
end if
if dice > 1 then
#main "up; goto 155 35"
#main "down; circlefilled 5"
#main "up; goto 205 85"
#main "down; circlefilled 5"
end if
if dice > 3 then
#main "up; goto 205 35"
#main "down; circlefilled 5"
#main "up; goto 155 85"
#main "down; circlefilled 5"
end if
if dice = 6 then
#main "up; goto 155 60"
#main "down; circlefilled 5"
#main "up; goto 205 60"
#main "down; circlefilled 5"
end if
return
As a final touch I want to add the sound of rolling dice to the program and cause the dice to show several dice faces as it settles down so that it will look more random. I have a nice clip of dice rolling that I picked up somewhere that I will include with the ZIP archive of the newsletter. It is called Dice01.wav.
Playing wave files is very easy from Liberty Basic. The PLAYWAVE statement lets us do it. It requires the wave file name (including path if it is not in the same directory as your program is running from) and an optional argument describing how to play the wave file. Here is a clip from the Liberty Basic helpfile which describes the statement well.
PLAYWAVE "filename" [, mode ]
Description:
Plays a *.wav sound file as specified in filename. If mode is specified, it must be one of the modes specifed below:
sync (or synch) - wait for the wave file to finish playing (the default) async (or asynch) - don't wait for the wave file to finish playing loop - play the wave file over and over (cancel with: playwave "")
Usage:
playwave "ding.wav", async playwave "tada.wav" playwave "hello.wav", loop playwave "" 'to stop previous wav from playing
Liberty Basic can only play one wave file at a time. It can be played in one of three modes as described in the information above. We will want to play our wave file as the dice rolling code runs, so that means we will want to use the asynch mode of play.
The command we will use (presuming you have saved the source code for dice roll and the wave file in the same directory) is:
Playwave "dice01.wav", asynch
We will make this the first command executed after the [roll] event handler. The next thing we will do is loop through the rolling and display of rolling dice. I originally did this five times, but later decided to choose a random number of times to loop through the code, as few as three times and as many as seven times. The code fragment that selects how many times to loop looks like this:
times = int(rnd(0)*4)+4
Then I set up a loop using a FOR-NEXT looping construct. We have discussed this form of flow control in earlier issues, but a quick review is in order. We will turn to the Liberty Basic helpfile for the details of the command (partial excerpt):
FOR...NEXT
Description:
The FOR . . . NEXT looping construct provides a way to execute code a specific amount of times. A starting and ending value are specified like so:
for var = 1 to 10
{BASIC code}
next var
In this case, the {BASIC code} is executed 10 times, with var being 1 the first time, 2 the second, and on through 10 the tenth time. Optionally (and usually) var is used in some calculation(s) in the {BASIC code}. For example if the {BASIC code} is print var ^ 2, then a list of squares for var will be displayed upon execution.
The specified range could just as easily be 2 TO 20, instead of 1 TO 10, but since the loop always counts +1 at a time, the first number must be less than the second.
So, using our times variable which we have loaded with a random value representing how many times to through the dice, we will write the following FOR-NEXT statements:
for j = 1 to times
'put the code to roll the dice in here
next j
This will work, but on a fast computer it will flash by very, very fast. The last thing I added to each loop is a delay of 100 milliseconds before the "next j" portion of the code. My delay code is a bit advanced, so I am going to simply present it, and let it stand on its own. We have not yet discussed the WHILE-WEND looping structure used here, or the TIME command, so take on faith that the next two lines of code will cause Liberty Basic to pause for about 100 milliseconds. We will break this code down in another installment of this series:
t = time$("ms")
while time$("ms") < t + 100 : wend
All put together, the new and improved event handler for the [roll] event looks like the following:
[roll]
'roll a random number of times
playwave "dice01.wav", async
times = int(rnd(0)*4)+4
for j = 1 to times
'clear any used memory
#main "discard"
'clear last dice
#main "backcolor white"
#main "up; goto 20 20; down; boxfilled 100 100; up"
#main "goto 140 20; down; boxfilled 220 100; up"
'choose some dice values
dice = int(rnd(0)*6)+1
gosub [showDice1]
dice = int(rnd(0)*6)+1
gosub [showDice2]
'put a loop in here to pause for a brief moment (100 millisec)
t = time$("ms")
while time$("ms") < t + 100 : wend
next j
wait
The entire program is in Appendix B, and is in source format in the newsletter archive along with the sound file.
The Challenge
What is a Beginner Series Article with out the challenge section. This time I am challenging you to look back over the earlier installments of the series where we developed the console based Craps game, and applying the same rules and techniques along with the GUI techniques we have developed here and make a GUI based Craps game.
As a reminder, here are the basic rules we used in the earlier efforts to create a Craps game:
Officially we will again be using a thin version of the casino game Craps, and again I am not condoning gambling. This simple version of the game does not include any betting, merely the random qualities and basic rules for the play of Craps that result in either winning or losing.
Here are the basic rules we will be using: Two dice are used. Initial roll of the dice is to establish the "point" which you will be trying to roll again. Rolling a 7 or 11 with your first roll is an automatic win. Rolling a 2, 3 or 12 with your initial roll is an automatic loss. An initial roll of 4, 5, 6, 8, 9 or 10 establishes the "point".
Once a point is established the player must roll the dice again (repeatedly until they win or lose). Rolling the "point" again is a win. Rolling a 7 is a loss. Have fun!